// 这个模块实现了一个应用服务器，用来处理我们的客户端请求

import { Router, AsyncCb, Route } from "./router"
import { IncomingForm } from "formidable"
import { BbsError, NotFoundError, InternalServerError } from "./error"
import * as http from "http"
import * as url from "url"
import * as qs from "querystring"

export class Settings {
  // 服务器配置
  constructor(
    public keepExtensions = true,               // 上传参数：是否保留文件扩展名 
    public uploadDir = "/tmp",                  // 上传参数：上传存放的临时目录
    public multiples = true,                    // 上传参数：是否允许同时上传多个文件
    public maxFieldsSize = 2 * 1024 * 1024,     // 上传参数：每个字段的最大长度
    public maxFields = 1000,                    // 上传参数：允许的字段个数 
    public autoRouteError = true                // 自动处理路由错误 
  ) {}
}

export interface IncomingMessage extends http.IncomingMessage {
  uri: url.Url 
  files?: any
  fields?: any
  characters?: string
  params?: any
  cookies: any
}

export interface ServerResponse extends http.ServerResponse {
}

function overwiteRequest(req: IncomingMessage) {
  // 重写请求对象，增加我们需要的各个配件
  req.uri = url.parse(decodeURIComponent(req.url), true)
  req.method = req.uri.query.__method 
               ? req.uri.query.__method.toUpperCase() 
               : req.method.toUpperCase()
  req.cookies = qs.parse(req.headers.cookie, '; ', '=') // object, 请求 Cookie --- 保存着会话信息
}

function getOrDefault(header: string, content: string) {
  if (typeof header === "string" && header.search(content) > -1) {
    return true
  }
  return false
}

export class CoreServer extends http.Server {
  private settings: Settings
  private routers: Array<Router>

  constructor(settings = new Settings()) {
    super()
    this.routers = []
    this.settings = settings
    var server = this

    async function execRoute(req: IncomingMessage, res: ServerResponse, route: Route) {
      try {
        console.log(req.method, req.uri.path)
        await route.exec(req, res)
      } catch (e) {
        if (!(e instanceof BbsError)) {
          e = new InternalServerError(e.message)
        }
        server.emit("routeError", req, res, e)
      }
    }
    
    function parseForm(req: IncomingMessage, res: ServerResponse, cb: any) {
      var form = new IncomingForm()
      form.keepExtensions = server.settings.keepExtensions
      form.uploadDir = server.settings.uploadDir
      form.maxFieldsSize = server.settings.maxFieldsSize
      form.maxFields = server.settings.maxFields
      form.multiples = server.settings.multiples
      req.fields = {}
      if (getOrDefault(req.headers["content-type"], "multipart/form-data")) {
        req.files = {}
      }
      form
        .on("error", function(err: Error) {
          server.emit("routeError", req, res, new InternalServerError(err.message))
        })
        .on("file", function(name: string, file: File) {
          if (!(name in req.files)) {
            req.files[name] = []
          }
          req.files[name].push(file)
        })
        .on("field", function(name: string, value: any) {
          req.fields[name] = value
        })
        .on("end", cb)
        .parse(req)
    }

    function parseCharacters(req: IncomingMessage, res: ServerResponse, cb: any) {
      req.characters = ""
      req
        .on("data", function(data) {
          req.characters += data
        })
        .on("end", cb)
    }

    server.on("request", function(req: IncomingMessage, res: ServerResponse) {
      overwiteRequest(req)
      var route: Route = null, params: {[key: string]: any}
      for (var router of this.routers) {
        [route, params] = router.find(req.method, req.uri.pathname)
        if (route !== null) {
          req.params = params
          if (getOrDefault(req.headers["content-type"], "application/json") ||
            getOrDefault(req.headers["content-type"], "multipart/form-data") ||
            getOrDefault(req.headers["content-type"], "application/x-www-form-urlencoded") ||
            getOrDefault(req.headers["content-type"], "multipart/form-data")) {
            parseForm(req, res, () => execRoute(req, res, route))
          } else {
            parseCharacters(req, res, () => execRoute(req, res, route))
          }
          break
        }
      }
      if (route === null) {
        server.emit("routeError", req, res, new NotFoundError("not found"))
      } 
    })

    if (settings.autoRouteError) {
      server.on('routeError', function(req: IncomingMessage, res: ServerResponse, err: BbsError) {
        if (!res.finished) {
          if (!res.headersSent) {
            res.writeHead(err.statusCode, { 'Content-Type': 'text/plain' })
          }
          res.end(err.message)
        }
      })
    }
  }

  public bindRouter(router: Router): CoreServer {
    this.routers.push(router)
    return this
  }
}
